socket套接字记录 您所在的位置:网站首页 socket inaddr_any socket套接字记录

socket套接字记录

2023-05-10 07:46| 来源: 网络整理| 查看: 265

socket在日常工作中用的比较多,但是之前接触的比较少,需要总结一下,这里做一下记录:

套接字是用来数据交互的,也是一种IO操作,Unix/linux系统中,为了统一各种硬件的操作,简化接口,不同的硬件设备看成一个文件,对这些文件的操作,等同对磁盘上普通文件的操作。为了表示和区分已经打开的文件,给每个文件分配一个id,这个id是一个整数,称为文件描述符,程序在执行任何形式的io操作的时候,都是在读取或者写入一个文件描述符,一个文件描述符只是一个和打开的文件相关联的整数,他的背后可能是一个硬盘上的普通文件,终端或者是网络连接。

1、套接字的种类

套接字有很多种,例如unix套接字,internet套接字,目前用到的就这两种,主要记录这两种。

2、internet套接字

根据数据的传输方式,可以将internet套接字分成两种类型,在通过socket()函数创建创建连接的时候,必须告诉它使用哪种数据传输方式。

流格式套接字(SOCK_STREAM)也叫面向连接的套接字,是一种可靠的。双向的通信数据流,数据可以准确的到达另一台计算机,损坏或者丢失,可以重新发送。(里面有自己的内部纠错机制),使用的是TCP协议,TCP协议会控制你的数据按照顺序到达并且没有错误。 数据报格式套接字(SOCK_DGRAM)也叫无连接的套接字,计算机只管传输数据,如果数据在传输中损坏,或者没有到达另一台计算机,是无法补救的,数据错了无法重传!

socket() 函数用来创建套接字,确定套接字的各种属性,然后服务器端要用 bind() 函数将套接字与特定的 IP 地址和端口绑定起来,只有这样,流经该 IP 地址和端口的数据才能交给套接字处理。类似地,客户端也要用 connect() 函数建立连接。

函数用法示例如下:

int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

之后就是设置参数了,发送方和接收方要设置不同的参数:

下面是接收方设置 image 下面是发送方设置 image

可以看到这里都是用sockaddr_in结构体,之后在强制转为sock_addr的类型,因为sock_addr是一种统一的标准,而sock_addr_in是专门针对ipv4的,相关的结构体说明如下所示:

struct sockaddr_in{ sa_family_t sin_family; //地址族(Address Family),也就是地址类型 uint16_t sin_port; //16位的端口号 struct in_addr sin_addr; //32位IP地址 char sin_zero[8]; //不使用,一般用0填充 };

对于服务器端程序,使用 bind() 绑定套接字后,还需要使用 listen() 函数让套接字进入被动监听状态,再调用 accept() 函数,就可以随时响应客户端的请求了。

通过listen()函数使套接字进入被动监听状态(没有客户端连接时,套接字处于睡眠状态,只有当接收到客户端请求的时候,才会被唤醒起来响应请求)

当套接字处于监听状态时,可以通过 accept() 函数来接收客户端请求。它的参数与 listen() 和 connect() 是相同的,sock 为服务器端套接字,addr 为 sockaddr_in 结构体变量,addrlen 为参数 addr 的长度,可由 sizeof() 求得。

接收消息示例如下所示

image

accept() 返回一个新的套接字来和客户端通信,addr 保存了客户端的IP地址和端口号,而 sock 是服务器端的套接字,后面和客户端通信时,要使用这个新生成的套接字,而不是原来服务器端的套接字。 listen() 只是让套接字进入监听状态,并没有真正接收客户端请求,listen() 后面的代码会继续执行,直到遇到 accept()。accept() 会阻塞程序执行(后面代码不能被执行),直到有新的请求到来。

3、主机字节序和网络字节序

前面给结构体设置参数的时候涉及到了这个部分内容,因此这里做一下补充:

主机字节序和网络字节序是两种不同的字节序。主机字节序是指在计算机内部处理数据时采用的字节序,而网络字节序是指在计算机网络中传输数据时采用的字节序。

在网络传输数据时,不同的计算机可能采用不同的主机字节序,因此需要一种公共的字节序来保证数据的正确传输。因此,网络字节序通常采用大端字节序(也称为“网络序”),即高位字节在前,低位字节在后。

比如上面用到的htons(),htons() 用来将当前主机字节序转换为网络字节序,其中h代表主机(host)字节序,n代表网络(network)字节序,s代表short,htons 是 h、to、n、s 的组合,可以理解为”将 short 型数据从当前主机字节序转换为网络字节序。

常见的网络字节转换函数有:

htons():host to network short,将 short 类型数据从主机字节序转换为网络字节序。 ntohs():network to host short,将 short 类型数据从网络字节序转换为主机字节序。 htonl():host to network long,将 long 类型数据从主机字节序转换为网络字节序。 ntohl():network to host long,将 long 类型数据从网络字节序转换为主机字节序。

是基于internet套接字的完整示例代码

server.c

#include #include #include #include #include #include #include int main(){ int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); struct sockaddr_in serv_addr; memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); serv_addr.sin_port = htons(1234); bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); listen(serv_sock, 20); struct sockaddr_in clnt_addr; socklen_t clnt_addr_size = sizeof(clnt_addr); int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size); char str[] = "send message"; write(clnt_sock, str, sizeof(str)); close(clnt_sock); close(serv_sock); return 0; }

client.c

#include #include #include #include #include #include #include int main(){ int sock = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in serv_addr; memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); serv_addr.sin_port = htons(1234); connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); char buffer[40]; read(sock, buffer, sizeof(buffer) - 1); printf("message form server %s\n", buffer); close(sock); return 0; }

可以改进一点实现循环收发: server.c

#include #include #include #include #include #include #include int main(){ int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); struct sockaddr_in serv_addr; memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); serv_addr.sin_port = htons(1234); bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); listen(serv_sock, 20); struct sockaddr_in clnt_addr; socklen_t clnt_addr_size = sizeof(clnt_addr); char buffer[100] = {0}; while (1) { int clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_addr, &clnt_addr_size); int strlen = recv(clnt_sock, buffer, 100, 0); send(clnt_sock, buffer, strlen, 0); close(clnt_sock); memset(buffer, 0 ,100); } close(serv_sock); return 0; }

client.c

#include #include #include #include #include #include #include int main(){ struct sockaddr_in serv_addr; memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); serv_addr.sin_port = htons(1234); char bufsend[100] = {0}; char bufrecv[100] = {0}; while (1) { int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); printf("input send message:"); scanf("%s", bufsend); send(sock, bufsend, strlen(bufsend), 0); recv(sock, bufrecv, 100, 0); printf("message form server %s\n", bufrecv); memset(bufsend, 0, 100); memset(bufrecv, 0, 100); close(sock); } return 0; } 4、unix套接字

一般流程为: ocket函数创建了一个Unix socket,并使用bind函数将其绑定到指定的文件路径上。然后通过listen函数监听客户端连接请求,并使用accept函数接受客户端连接。最后,通过recv函数从客户端接收数据,并在控制台输出。整个过程完成后,需要使用close函数关闭套接字,释放资源。

注意,Unix socket只能用于本地进程间通信,不能用于网络通信。此外,在使用Unix socket进行进程间通信时,需要确保各进程所使用的文件路径相同,否则无法建立连接。

下面是完整示例:

client.c

#include #include #include #include #include #define SOCKET_PATH "/tmp/socket" int main(void) { int sockfd; struct sockaddr_un servaddr; sockfd = socket(AF_UNIX, SOCK_STREAM, 0); if (sockfd == -1) { perror("socket error"); exit(EXIT_FAILURE); } memset(&servaddr, 0, sizeof(servaddr)); servaddr.sun_family = AF_UNIX; strncpy(servaddr.sun_path, SOCKET_PATH, sizeof(servaddr.sun_path)-1); if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) { perror("connect error"); exit(EXIT_FAILURE); } // 发送数据 char buf[] = "Hello, Unix Socket!"; ssize_t n = send(sockfd, buf, sizeof(buf), 0); if (n == -1) { perror("send error"); exit(EXIT_FAILURE); } close(sockfd); return 0; }

server.c

#include #include #include #include #include #define SOCKET_PATH "/tmp/socket" int main(void) { int sockfd; struct sockaddr_un servaddr; char buf[1024]; sockfd = socket(AF_UNIX, SOCK_STREAM, 0); if (sockfd == -1) { perror("socket error"); exit(EXIT_FAILURE); } memset(&servaddr, 0, sizeof(servaddr)); servaddr.sun_family = AF_UNIX; strncpy(servaddr.sun_path, SOCKET_PATH, sizeof(servaddr.sun_path)-1); unlink(SOCKET_PATH); if (bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) { perror("bind error"); exit(EXIT_FAILURE); } if (listen(sockfd, 5) == -1) { perror("listen error"); exit(EXIT_FAILURE); } for (;;) { int connfd = accept(sockfd, NULL, NULL); if (connfd == -1) { perror("accept error"); continue; } // 处理连接请求 int nbytes = recv(connfd, buf, sizeof(buf), 0); if (nbytes == -1) { perror("recv"); exit(EXIT_FAILURE); } printf("Received message: %s\n", buf); close(connfd); } close(sockfd); unlink(SOCKET_PATH); return 0; } 5、netlink套接字

netlink常用于用户态和内核态之间进行通信,下面是一个netlink套接字的示例

内核态:

#include #include #include #include #include #define NETLINK_TEST 30 #define MSG_LEN 125 #define USER_PORT 100 MODULE_LICENSE("GPL"); MODULE_AUTHOR("zhangwj"); MODULE_DESCRIPTION("netlink example"); struct sock *nlsk = NULL; extern struct net init_net; int send_usrmsg(char *pbuf, uint16_t len) { struct sk_buff *nl_skb; struct nlmsghdr *nlh; int ret; /* 创建sk_buff 空间 */ nl_skb = nlmsg_new(len, GFP_ATOMIC); if(!nl_skb) { printk("netlink alloc failure\n"); return -1; } /* 设置netlink消息头部 */ nlh = nlmsg_put(nl_skb, 0, 0, NETLINK_TEST, len, 0); if(nlh == NULL) { printk("nlmsg_put failaure \n"); nlmsg_free(nl_skb); return -1; } /* 拷贝数据发送 */ memcpy(nlmsg_data(nlh), pbuf, len); ret = netlink_unicast(nlsk, nl_skb, USER_PORT, MSG_DONTWAIT); return ret; } static void netlink_rcv_msg(struct sk_buff *skb) { struct nlmsghdr *nlh = NULL; char *umsg = NULL; char *kmsg = "hello users!!!"; if(skb->len >= nlmsg_total_size(0)) { nlh = nlmsg_hdr(skb); umsg = NLMSG_DATA(nlh); if(umsg) { printk("kernel recv from user: %s\n", umsg); send_usrmsg(kmsg, strlen(kmsg)); } } } struct netlink_kernel_cfg cfg = { .input = netlink_rcv_msg, /* set recv callback */ }; int test_netlink_init(void) { /* create netlink socket */ nlsk = (struct sock *)netlink_kernel_create(&init_net, NETLINK_TEST, &cfg); if(nlsk == NULL) { printk("netlink_kernel_create error !\n"); return -1; } printk("test_netlink_init\n"); return 0; } void test_netlink_exit(void) { if (nlsk){ netlink_kernel_release(nlsk); /* release ..*/ nlsk = NULL; } printk("test_netlink_exit!\n"); } module_init(test_netlink_init); module_exit(test_netlink_exit);

用户态

#include #include #include #include #include #include #include #include #define NETLINK_TEST 30 #define MSG_LEN 125 #define MAX_PLOAD 125 typedef struct _user_msg_info { struct nlmsghdr hdr; char msg[MSG_LEN]; } user_msg_info; int main(int argc, char **argv) { int skfd; int ret; user_msg_info u_info; socklen_t len; struct nlmsghdr *nlh = NULL; struct sockaddr_nl saddr, daddr; char *umsg = "hello netlink!!"; /* 创建NETLINK socket */ skfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST); if(skfd == -1) { perror("create socket error\n"); return -1; } memset(&saddr, 0, sizeof(saddr)); saddr.nl_family = AF_NETLINK; //AF_NETLINK saddr.nl_pid = 100; //端口号(port ID) saddr.nl_groups = 0; if(bind(skfd, (struct sockaddr *)&saddr, sizeof(saddr)) != 0) { perror("bind() error\n"); close(skfd); return -1; } memset(&daddr, 0, sizeof(daddr)); daddr.nl_family = AF_NETLINK; daddr.nl_pid = 0; // to kernel daddr.nl_groups = 0; nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PLOAD)); memset(nlh, 0, sizeof(struct nlmsghdr)); nlh->nlmsg_len = NLMSG_SPACE(MAX_PLOAD); nlh->nlmsg_flags = 0; nlh->nlmsg_type = 0; nlh->nlmsg_seq = 0; nlh->nlmsg_pid = saddr.nl_pid; //self port memcpy(NLMSG_DATA(nlh), umsg, strlen(umsg)); ret = sendto(skfd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&daddr, sizeof(struct sockaddr_nl)); if(!ret) { perror("sendto error\n"); close(skfd); exit(-1); } printf("send kernel:%s\n", umsg); memset(&u_info, 0, sizeof(u_info)); len = sizeof(struct sockaddr_nl); ret = recvfrom(skfd, &u_info, sizeof(user_msg_info), 0, (struct sockaddr *)&daddr, &len); if(!ret) { perror("recv form kernel error\n"); close(skfd); exit(-1); } printf("from kernel:%s\n", u_info.msg); close(skfd); free((void *)nlh); return 0; } 6、套接字监听

当有很多套接字同时工作的时候,由于接收函数是阻塞的,这样是很不方便的,因此最好是能有一种类似中断的机制,于是可以使用监听的方案来提高效率,常用的有select,poll,和epoll这几种。select、poll和epoll都是Linux系统中的I/O多路复用机制,可以同时监听多个文件描述符,当其中有可读或可写事件时进行处理。

它们之间的主要区别如下:

select和poll的缺点:当需要监听大量的文件描述符时,每次调用select或poll都需要将所有的文件描述符从用户空间复制到内核空间,这个操作的开销比较大。此外,在处理大量文件描述符时,每次调用select或poll都需要遍历整个文件描述符集合,导致效率低下。

epoll的优点:epoll使用了更高效的数据结构来存储文件描述符,因此能够支持成千上万个文件描述符的监听,而不会出现性能问题。此外,epoll提供了三种工作模式(水平触发、边缘触发和信号驱动)以及更细粒度的控制选项,使其比select和poll更加灵活和强大。

工作方式:select和poll采用轮询方式来检查是否有事件发生,而epoll通过回调函数来通知应用程序有哪些事件已经准备好了。

使用场景:对于少量的连接,select和poll可能是比较适合的选择;而当需要同时处理大量的连接时,epoll是更好的选择。

下面是具体的例子:

epoll.c

#include #include #include #include #include #include #define MAX_CLIENTS 10 int main(int argc, char *argv[]) { int server_sockfd, client_sockfd[MAX_CLIENTS]; struct sockaddr_in server_addr, client_addr; int client_len = sizeof(client_addr); int max_fd, fd_num, i, j; struct epoll_event ev, events[MAX_CLIENTS + 1]; int epollfd; // 创建TCP套接字 server_sockfd = socket(AF_INET, SOCK_STREAM, 0); // 初始化服务器地址结构体 server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(atoi(argv[1])); // 绑定套接字到服务器地址 bind(server_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)); // 监听客户端请求 listen(server_sockfd, MAX_CLIENTS); // 创建epoll文件描述符 epollfd = epoll_create(MAX_CLIENTS + 1); if (epollfd == -1) { perror("epoll_create"); exit(EXIT_FAILURE); } // 添加server_sockfd到epoll事件表 ev.events = EPOLLIN; ev.data.fd = server_sockfd; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, server_sockfd, &ev) == -1) { perror("epoll_ctl: server_sockfd"); exit(EXIT_FAILURE); } while (1) { // 监听epoll事件表 fd_num = epoll_wait(epollfd, events, MAX_CLIENTS + 1, -1); if (fd_num == -1) { perror("epoll_wait"); exit(EXIT_FAILURE); } for (i = 0; i < fd_num; i++) { if (events[i].data.fd == server_sockfd) { // 接受客户端连接请求 client_sockfd[i] = accept(server_sockfd, (struct sockaddr *)&client_addr, &client_len); if (client_sockfd[i] == -1) { perror("accept"); exit(EXIT_FAILURE); } // 添加client_sockfd到epoll事件表 ev.events = EPOLLIN; ev.data.fd = client_sockfd[i]; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, client_sockfd[i], &ev) == -1) { perror("epoll_ctl: client_sockfd"); exit(EXIT_FAILURE); } } else { // 处理客户端数据 // ... } } } // 关闭套接字 close(server_sockfd); for (j = 0; j < MAX_CLIENTS; j++) { if (client_sockfd[j] > 0) { close(client_sockfd[j]); } } return 0; }

poll.c

#include #include #include #include #include #include #define MAX_CLIENTS 10 int main(int argc, char *argv[]) { int server_sockfd, client_sockfd[MAX_CLIENTS]; struct sockaddr_in server_addr, client_addr; int client_len = sizeof(client_addr); int max_fd, fd_num, i, j; struct pollfd fds[MAX_CLIENTS + 1]; // 创建TCP套接字 server_sockfd = socket(AF_INET, SOCK_STREAM, 0); // 初始化服务器地址结构体 server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(atoi(argv[1])); // 绑绑定套接字到服务器地址 bind(server_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)); // 监听客户端请求 listen(server_sockfd, MAX_CLIENTS); // 初始化pollfd数组 fds[0].fd = server_sockfd; fds[0].events = POLLIN; for (i = 1; i


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有